Setup

Load libraries

library(ggplot2)
library(tidyr)
library(dplyr)
library(Matrix)
library(Seurat)
library(cowplot)
library(patchwork)

# parallelization
library(future)
options(future.globals.maxSize= +Inf)
plan()
sequential:
- args: function (expr, envir = parent.frame(), substitute = TRUE, lazy = FALSE, seed = NULL, globals = TRUE, local = TRUE, earlySignal = FALSE, label = NULL, ...)
- tweaked: FALSE
- call: NULL

Process Human Data

import_remote_data <- function(file_url, type = "table", header = FALSE) {
  con <- gzcon(url(file_url))
  txt <- readLines(con)
  if (type == "MM") { return (readMM(textConnection(txt))) }
  if (type == "table") { return (read.table(textConnection(txt), header = header)) }
}
count_matrix_URL <- "https://ftp.ncbi.nlm.nih.gov/geo/series/GSE137nnn/GSE137537/suppl/GSE137537_counts.mtx.gz"
gene_names_URL <- "https://ftp.ncbi.nlm.nih.gov/geo/series/GSE137nnn/GSE137537/suppl/GSE137537_gene_names.txt.gz"
sample_annotations_URL <- "https://ftp.ncbi.nlm.nih.gov/geo/series/GSE137nnn/GSE137537/suppl/GSE137537_sample_annotations.tsv.gz"

human.count_matrix <- as.matrix(import_remote_data(count_matrix_URL, type = "MM"))
human.gene_names <- import_remote_data(gene_names_URL, type = "table")
human.sample_annotations <- import_remote_data(sample_annotations_URL, type = "table", header = TRUE)
human_ret_seurat
An object of class Seurat 
19712 features across 20091 samples within 1 assay 
Active assay: RNA (19712 features, 0 variable features)

Process Mouse Data

mouse.data <- Read10X(data.dir = "filtered_feature_bc_matrix")
dimnames(mouse.data)[[1]] <- tolower(dimnames(mouse.data)[[1]])
dimnames(mouse.data)[[2]] <- tolower(dimnames(mouse.data)[[2]])
mouse_ret_seurat <- CreateSeuratObject(counts = mouse.data, 
                                       project = "mouse_ret", 
                                       min.cells = 3, 
                                       min.features = 200)
mouse_ret_seurat
An object of class Seurat 
16424 features across 4510 samples within 1 assay 
Active assay: RNA (16424 features, 0 variable features)

Process Primate Data

url=https://ftp.ncbi.nlm.nih.gov/geo/series/GSE118nnn/GSE118546/suppl/GSE118546_macaque_fovea_all_10X_Jan2018.Rdata.gz
wget $url -O primate_data/GSE118546_macaque_fovea_all_10X_Jan2018.Rdata.gz
gunzip primate_data/*
install.packages( c('devtools', 'roxygen2') )
library(devtools)
library(roxygen2)
install_github( 'hb-gitified/cellrangerRkit',
                auth_token = 'your_token' )
macaque_fovea_seurat
An object of class Seurat 
30039 features across 111993 samples within 1 assay 
Active assay: RNA (30039 features, 0 variable features)

Cleanup

rm(human.count_matrix, human.gene_names, human.sample_annotations)
rm(count_matrix_URL, gene_names_URL, sample_annotations_URL, import_remote_data)
rm(mouse.data)
rm(Count.mat_fovea, macaque_fovea)

Combine

# combine
ret.list <- list(human = human_ret_seurat, mouse = mouse_ret_seurat, macaque = macaque_fovea_seurat)

# preprocess
ret.list <- lapply(X = ret.list, FUN = function(x) {
    x <- NormalizeData(x, verbose = FALSE)
    x <- FindVariableFeatures(x, selection.method = "vst", nfeatures = 2000, verbose = FALSE)
})

# cleanup
rm(human_ret_seurat, mouse_ret_seurat, macaque_fovea_seurat)

Integration

plan("multiprocess", workers = 4)
ret.anchors <- FindIntegrationAnchors(object.list = ret.list, dims = 1:50,  anchor.features = 1000)
plan("multiprocess", workers = 1)
ret.combined <- IntegrateData(anchorset = ret.anchors, dims = 1:50)

Integrated Analysis

plan("multiprocess", workers = 4)

DefaultAssay(ret.combined) <- "integrated"

# Run the standard workflow for visualization and clustering
ret.combined <- ScaleData(ret.combined, verbose = FALSE)
ret.combined <- RunPCA(ret.combined, npcs = 50, verbose = FALSE)
# t-SNE and Clustering
ret.combined <- RunUMAP(ret.combined, reduction = "pca", dims = 1:35)
ret.combined <- FindNeighbors(ret.combined, reduction = "pca", dims = 1:35)
ret.combined <- FindClusters(ret.combined, resolution = 0.075)

UMAP Visualization

DimPlot(ret.combined, reduction = "umap", group.by = "orig.ident")

DimPlot(ret.combined, reduction = "umap", label = TRUE)

DimPlot(ret.combined, reduction = "umap", split.by = "orig.ident", ncol = 1)

Identify Clusters with Canonical Markers

DefaultAssay(ret.combined) <- "RNA"

features <- tolower(c("Pde6a","Gnat2","Nefl","Camk2b","Thy1","Gad1","Slc6a9",
                      "Pcsk6","Trpm1","Sept4","Glul","Arr3","C1qa","Tm4sf1", "Mgp"))

FeaturePlot(object = ret.combined, 
            features = features, 
            pt.size = 0.1,
            cols = c("lightgrey", "#F26969"),
            min.cutoff = "q9",
            combine = TRUE) & NoLegend() & NoAxes()


# Cowplot method: make sure to change to "combine = FALSE" and remove "& NoLegend() & NoAxes"

# for(i in 1:length(p)) {
#   p[[i]] <- p[[i]] + NoLegend() + NoAxes()
# }
# 
# cowplot::plot_grid(plotlist = p, ncol=3)

Markers were determined from this paper and other sources.

Find Differentially Expressed Genes

cells.types <- c("Rod", "BC", "MG", "RGC", "CC", "AC", "VC", "HC", "M")
theme_set(theme_cowplot())

cell_type_avg <- function(seurat.combined, ident) {
  cells.x <- subset(seurat.combined, idents = ident)
  Idents(cells.x) <- "orig.ident"
  cells.x.avg <- log1p(AverageExpression(cells.x, verbose = FALSE)$RNA)
  cells.x.avg$gene <- rownames(cells.x.avg)
  return(cells.x.avg)
}

cells.plot <- as.list(cells.types)
cells.plot <- lapply(cells.plot, FUN = function(x) {
  cells.x.avg <- cell_type_avg(ret.combined, ident = x)
  x <- ggplot(cells.x.avg, aes(human_ret, mouse_ret)) + geom_point(size = 0.1) + ggtitle(x)
  return(x)
})

# For individual plots
# for (p in cells.plot) {
#   print(p)
# }

# For grid plot
cowplot::plot_grid(plotlist = cells.plot, ncol = 3)

ret.combined$celltype.organism <- paste(Idents(ret.combined), ret.combined$orig.ident, sep = "_")
ret.combined$celltype <- Idents(ret.combined)
Idents(ret.combined) <- "celltype.organism"
cells.diffgenes <- as.list(cells.types)
cells.diffgenes <- lapply(cells.diffgenes, FUN = function(x) {
  lab_human <- sprintf("%s_human_ret", x)
  lab_mouse <- sprintf("%s_mouse_ret", x)
  return(FindMarkers(ret.combined, ident.1 = lab_human, ident.2 = lab_mouse))
})


for(i in seq_along(cells.diffgenes)) {
    x <- cells.diffgenes[[i]]
    x <- cbind(x, logp = -log10(x$p_val), types = cells.types[[i]], genes = rownames(x))
    x <- x[!grepl("mt-", x$genes),] # remove mitochondrial genes
    cells.diffgenes[[i]] <- x
    rm(x)
}

Tables with the most differentially expressed genes in each cell subtype:

for(i in seq_along(cells.diffgenes)) {
  print(knitr::kable(head(cells.diffgenes[[i]]),caption=cells.types[[i]]))
}

Rod
p_val avg_logFC pct.1 pct.2 p_val_adj logp types genes
ckb 0 1.4493770 0.918 0.724 0 Inf Rod ckb
hsp90aa1 0 1.3457646 0.854 0.627 0 Inf Rod hsp90aa1
nrl 0 1.3140138 0.874 0.635 0 Inf Rod nrl
0610009b22rik 0 -0.6622860 0.000 0.130 0 Inf Rod 0610009b22rik
gm17018 0 -0.6831275 0.000 0.130 0 Inf Rod gm17018
spata1 0 -0.6929677 0.000 0.132 0 Inf Rod spata1
BC
p_val avg_logFC pct.1 pct.2 p_val_adj logp types genes
neat1 0 3.086391 0.793 0.064 0 Inf BC neat1
mtch1 0 -1.305054 0.000 0.459 0 Inf BC mtch1
selenom 0 -1.338108 0.000 0.480 0 Inf BC selenom
araf 0 -1.342891 0.013 0.494 0 Inf BC araf
klc3 0 -1.424615 0.002 0.500 0 Inf BC klc3
pea15a 0 -1.427543 0.000 0.500 0 Inf BC pea15a
MG
p_val avg_logFC pct.1 pct.2 p_val_adj logp types genes
tf 0 5.089073 0.962 0.000 0 Inf MG tf
spp1 0 3.879036 0.847 0.003 0 Inf MG spp1
crabp1 0 3.865908 0.876 0.028 0 Inf MG crabp1
gpx3 0 3.736219 0.869 0.052 0 Inf MG gpx3
ftl 0 3.672007 0.877 0.000 0 Inf MG ftl
actg1 0 3.639157 0.905 0.026 0 Inf MG actg1
RGC
p_val avg_logFC pct.1 pct.2 p_val_adj logp types genes
malat1 0e+00 -5.358199 0.000 1.000 0.0000020 10.308117 RGC malat1
gm42418 0e+00 -5.948091 0.000 0.957 0.0000083 9.683676 RGC gm42418
ay036118 0e+00 -2.770196 0.000 0.826 0.0004565 7.944868 RGC ay036118
neat1 0e+00 5.237230 0.889 0.087 0.0007506 7.728937 RGC neat1
linc00599 1e-07 2.669694 0.815 0.000 0.0024960 7.207095 RGC linc00599
kcnq1ot1 1e-07 2.290579 0.926 0.261 0.0025965 7.189955 RGC kcnq1ot1
CC
p_val avg_logFC pct.1 pct.2 p_val_adj logp types genes
gm42418 0 -5.444663 0.000 1.000 0 80.68864 CC gm42418
malat1 0 -5.893437 0.000 1.000 0 80.68864 CC malat1
ay036118 0 -2.515711 0.000 0.943 0 74.09104 CC ay036118
gnat2 0 -2.832927 0.117 0.943 0 66.80767 CC gnat2
mir124a-1hg 0 -2.420164 0.000 0.869 0 65.97475 CC mir124a-1hg
gngt2 0 -3.274161 0.143 0.937 0 64.88201 CC gngt2
AC
p_val avg_logFC pct.1 pct.2 p_val_adj logp types genes
gm42418 0 -5.868449 0 1.000 0 112.09869 AC gm42418
malat1 0 -6.139436 0 0.994 0 111.15686 AC malat1
ay036118 0 -2.993976 0 0.935 0 102.83653 AC ay036118
snhg11 0 -3.730037 0 0.916 0 100.12454 AC snhg11
ac149090.1 0 -2.375670 0 0.729 0 75.41475 AC ac149090.1
c230004f18rik 0 -2.693746 0 0.729 0 75.41474 AC c230004f18rik
VC
p_val avg_logFC pct.1 pct.2 p_val_adj logp types genes
hla-b 0 3.429125 0.884 0.00 0 49.06339 VC hla-b
rps3a 0 2.938618 0.826 0.00 0 45.19653 VC rps3a
hla-e 0 3.220179 0.826 0.00 0 45.19650 VC hla-e
hla-a 0 3.030967 0.812 0.00 0 44.24753 VC hla-a
hla-c 0 2.913989 0.797 0.00 0 43.30557 VC hla-c
a2m 0 3.322554 0.797 0.01 0 41.81784 VC a2m
HC
p_val avg_logFC pct.1 pct.2 p_val_adj logp types genes
gm42418 0 -6.046691 0.000 1.000 0 47.10148 HC gm42418
malat1 0 -5.904074 0.000 0.942 0 43.58186 HC malat1
neat1 0 4.028649 0.967 0.043 0 30.82663 HC neat1
ay036118 0 -2.922930 0.000 0.710 0 30.68474 HC ay036118
pvalb 0 3.437120 0.902 0.000 0 27.66539 HC pvalb
gpi1 0 -2.385417 0.000 0.580 0 24.19037 HC gpi1
M
p_val avg_logFC pct.1 pct.2 p_val_adj logp types genes
ftl 0 4.612295 0.98 0 0 35.06941 M ftl
hla-dra 0 4.664191 0.94 0 0 33.25514 M hla-dra
hla-a 0 2.899218 0.94 0 0 33.25514 M hla-a
hla-drb1 0 4.096260 0.92 0 0 32.36374 M hla-drb1
rps3a 0 3.397725 0.92 0 0 32.36374 M rps3a
hla-b 0 3.163581 0.92 0 0 32.36374 M hla-b

Save as csv files

for(i in seq_along(cells.diffgenes)) {
  write.csv(cells.diffgenes[[i]], sprintf("results/%d_%s.csv", i, cells.types[[i]]))
}
genes_to_plot <- 3
for (i in seq_along(cells.types)) {
  print(FeaturePlot(object = ret.combined, 
              features = rownames(cells.diffgenes[[i]])[1:genes_to_plot], 
              split.by = "orig.ident", 
              max.cutoff = 3, 
              cols = c("grey", "red"),
              pt.size = 0.07,
              combine = TRUE,
              label.size = 0.5
              ) + plot_annotation(title = cells.types[[i]]) & NoLegend() & NoAxes()
        )
}

Check cell proportion for each species:

knitr::kable(prop.table(x = table(Idents(ret.combined), ret.combined@meta.data$orig.ident), margin = 2))
human_ret macaque_fovea mouse_ret
0 0.2627047 0.1535810 0.2875831
1 0.5498980 0.0758172 0.3164080
2 0.0001493 0.1855205 0.0002217
3 0.0009955 0.1458216 0.0035477
4 0.0531581 0.0808176 0.0838137
5 0.0114977 0.0783888 0.0388027
6 0.0406650 0.0552267 0.0569845
7 0.0187148 0.0577625 0.0343681
8 0.0484794 0.0443242 0.0917960
9 0.0001493 0.0342075 0.0000000
10 0.0000498 0.0232247 0.0004435
11 0.0076154 0.0156081 0.0152993
12 0.0000000 0.0117775 0.0000000
13 0.0034344 0.0089827 0.0436807
14 0.0000000 0.0109203 0.0000000
15 0.0000000 0.0101435 0.0008869
16 0.0024887 0.0039645 0.0261641
17 0.0000000 0.0039110 0.0000000

Gene Enrichment Analysis

library(ggplot2)
library(ggrepel)
library(scales)
library(data.table)
cells.diffgenes.combined <- rbindlist(cells.diffgenes)

# Preprocessing
# for(i in seq_along(cells.diffgenes.combined)) {
#   if (cells.diffgenes.combined[i]$logp > 750) {
#       cells.diffgenes.combined[i]$logp <- 749
#   }
#   if (cells.diffgenes.combined[i]$avg_logFC > 3) {
#       cells.diffgenes.combined[i]$avg_logFC <- 2.99
#   }
# }
# 
# cells.diffgenes.combined$logp <- gsub("Inf", 749, cells.diffgenes.combined$logp)

ggplot(data=cells.diffgenes.combined, 
           aes(x=avg_logFC,y=logp, colour=types, label = genes)) + 
    geom_point(size=0.2) + 
    theme_bw() + 
    theme(panel.background = element_rect(fill = NA), 
          axis.ticks.x = element_blank(),  
          axis.text.y = element_text(size = 12), 
          panel.grid.major = element_blank(), 
          panel.grid.minor = element_blank()) + 
    labs(x = "log2(Fold changes)\n(3K/WT)", y ="-log10(p value)") +
    scale_x_continuous(limits=c(-3, 3)) +
    scale_y_continuous(limits=c(1, 300)) +
    geom_hline(yintercept= 1, colour="grey", linetype="dashed", size=0.7 ) +
    geom_vline(xintercept= 0 , colour="grey",   size=0.7)

library(stringr)
plot_enrichment <- function(type = "Rod", info = "") {
    if (info != "") { info.str <- sprintf("_%s", info) }
    else {info.str <- ""}
    file_path <- sprintf("enrich_data/%s%s.txt", type, info.str)
    x <- read.table(file_path, header=T, sep="\t", skip = 11)
    colnames(x) <- gsub("upload_1..fold.Enrichment.", "Fold_Enrichment", colnames(x))
    colnames(x) <- gsub("upload_1..FDR.", "FDR", colnames(x))
    colnames(x) <- gsub("GO.biological.process.complete", "GO", colnames(x))
    colnames(x) <- gsub("Homo.sapiens...REFLIST..20851.", "Count", colnames(x))
    x$GO<- factor(x$GO, levels = x$GO[order(x$Fold_Enrichment, decreasing =F)])
    x <- x[order(x$FDR),]
    x <- x[1:10,]
    g<- ggplot(data=x, aes(x=Fold_Enrichment, y=GO, colour=FDR)) + 
        geom_point(aes(size=Count)) + 
        theme_bw() +
        theme(panel.background = element_rect(fill = NA) , 
              axis.ticks.x=element_blank(), 
              axis.text.y = element_text(size = 12) , 
              panel.grid.major = element_blank(), 
              panel.grid.minor = element_blank()) + 
        labs(x = "Fold Enrichment", y =" ") +
        scale_colour_gradient(low = "red", high = "blue") +
        scale_y_discrete(labels = function(x) str_wrap(x, width = 50)) +
        ggtitle(type, info)
    return(g)
}

for (type in cells.types[-c(6,4)]) {
    print(plot_enrichment(type = type, info = "top200"))
    rm(type)
}

plot_enrichment(type = "RGC")

plot_enrichment(type = "AC")

LS0tCnRpdGxlOiAiSW50ZWdyYXRpbmcgUHJpbWF0ZSBEYXRhIGludG8gQW5hbHlzaXMiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQotLS0KIyBTZXR1cApMb2FkIGxpYnJhcmllcwpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoTWF0cml4KQpsaWJyYXJ5KFNldXJhdCkKbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KHBhdGNod29yaykKCiMgcGFyYWxsZWxpemF0aW9uCmxpYnJhcnkoZnV0dXJlKQpvcHRpb25zKGZ1dHVyZS5nbG9iYWxzLm1heFNpemU9ICtJbmYpCnBsYW4oKQpgYGAKUHJvY2VzcyBIdW1hbiBEYXRhCmBgYHtyfQppbXBvcnRfcmVtb3RlX2RhdGEgPC0gZnVuY3Rpb24oZmlsZV91cmwsIHR5cGUgPSAidGFibGUiLCBoZWFkZXIgPSBGQUxTRSkgewogIGNvbiA8LSBnemNvbih1cmwoZmlsZV91cmwpKQogIHR4dCA8LSByZWFkTGluZXMoY29uKQogIGlmICh0eXBlID09ICJNTSIpIHsgcmV0dXJuIChyZWFkTU0odGV4dENvbm5lY3Rpb24odHh0KSkpIH0KICBpZiAodHlwZSA9PSAidGFibGUiKSB7IHJldHVybiAocmVhZC50YWJsZSh0ZXh0Q29ubmVjdGlvbih0eHQpLCBoZWFkZXIgPSBoZWFkZXIpKSB9Cn0KY291bnRfbWF0cml4X1VSTCA8LSAiaHR0cHM6Ly9mdHAubmNiaS5ubG0ubmloLmdvdi9nZW8vc2VyaWVzL0dTRTEzN25ubi9HU0UxMzc1Mzcvc3VwcGwvR1NFMTM3NTM3X2NvdW50cy5tdHguZ3oiCmdlbmVfbmFtZXNfVVJMIDwtICJodHRwczovL2Z0cC5uY2JpLm5sbS5uaWguZ292L2dlby9zZXJpZXMvR1NFMTM3bm5uL0dTRTEzNzUzNy9zdXBwbC9HU0UxMzc1MzdfZ2VuZV9uYW1lcy50eHQuZ3oiCnNhbXBsZV9hbm5vdGF0aW9uc19VUkwgPC0gImh0dHBzOi8vZnRwLm5jYmkubmxtLm5paC5nb3YvZ2VvL3Nlcmllcy9HU0UxMzdubm4vR1NFMTM3NTM3L3N1cHBsL0dTRTEzNzUzN19zYW1wbGVfYW5ub3RhdGlvbnMudHN2Lmd6IgoKaHVtYW4uY291bnRfbWF0cml4IDwtIGFzLm1hdHJpeChpbXBvcnRfcmVtb3RlX2RhdGEoY291bnRfbWF0cml4X1VSTCwgdHlwZSA9ICJNTSIpKQpodW1hbi5nZW5lX25hbWVzIDwtIGltcG9ydF9yZW1vdGVfZGF0YShnZW5lX25hbWVzX1VSTCwgdHlwZSA9ICJ0YWJsZSIpCmh1bWFuLnNhbXBsZV9hbm5vdGF0aW9ucyA8LSBpbXBvcnRfcmVtb3RlX2RhdGEoc2FtcGxlX2Fubm90YXRpb25zX1VSTCwgdHlwZSA9ICJ0YWJsZSIsIGhlYWRlciA9IFRSVUUpCmBgYApgYGB7cn0Kcm93bmFtZXMoaHVtYW4uY291bnRfbWF0cml4KSA8LSB0b2xvd2VyKGh1bWFuLmdlbmVfbmFtZXNbLDFdKQpjb2xuYW1lcyhodW1hbi5jb3VudF9tYXRyaXgpIDwtIHRvbG93ZXIoaHVtYW4uc2FtcGxlX2Fubm90YXRpb25zWywxXSkKCmh1bWFuX3JldF9zZXVyYXQgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IGh1bWFuLmNvdW50X21hdHJpeCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGEuZGF0YSA9IGh1bWFuLnNhbXBsZV9hbm5vdGF0aW9ucywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2plY3QgPSAiaHVtYW5fcmV0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5jZWxscyA9IDMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4uZmVhdHVyZXMgPSAyMDApCmh1bWFuX3JldF9zZXVyYXQKYGBgCgpQcm9jZXNzIE1vdXNlIERhdGEKYGBge3J9Cm1vdXNlLmRhdGEgPC0gUmVhZDEwWChkYXRhLmRpciA9ICJmaWx0ZXJlZF9mZWF0dXJlX2JjX21hdHJpeCIpCmRpbW5hbWVzKG1vdXNlLmRhdGEpW1sxXV0gPC0gdG9sb3dlcihkaW1uYW1lcyhtb3VzZS5kYXRhKVtbMV1dKQpkaW1uYW1lcyhtb3VzZS5kYXRhKVtbMl1dIDwtIHRvbG93ZXIoZGltbmFtZXMobW91c2UuZGF0YSlbWzJdXSkKbW91c2VfcmV0X3NldXJhdCA8LSBDcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gbW91c2UuZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2plY3QgPSAibW91c2VfcmV0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5jZWxscyA9IDMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4uZmVhdHVyZXMgPSAyMDApCm1vdXNlX3JldF9zZXVyYXQKYGBgCgpQcm9jZXNzIFByaW1hdGUgRGF0YQpgYGB7YmFzaH0KdXJsPWh0dHBzOi8vZnRwLm5jYmkubmxtLm5paC5nb3YvZ2VvL3Nlcmllcy9HU0UxMThubm4vR1NFMTE4NTQ2L3N1cHBsL0dTRTExODU0Nl9tYWNhcXVlX2ZvdmVhX2FsbF8xMFhfSmFuMjAxOC5SZGF0YS5negp3Z2V0ICR1cmwgLU8gcHJpbWF0ZV9kYXRhL0dTRTExODU0Nl9tYWNhcXVlX2ZvdmVhX2FsbF8xMFhfSmFuMjAxOC5SZGF0YS5negpndW56aXAgcHJpbWF0ZV9kYXRhLyoKYGBgCmBgYHtyfQppbnN0YWxsLnBhY2thZ2VzKCBjKCdkZXZ0b29scycsICdyb3h5Z2VuMicpICkKbGlicmFyeShkZXZ0b29scykKbGlicmFyeShyb3h5Z2VuMikKaW5zdGFsbF9naXRodWIoICdoYi1naXRpZmllZC9jZWxscmFuZ2VyUmtpdCcsCiAgICAgICAgICAgICAgICBhdXRoX3Rva2VuID0gJ3lvdXJfdG9rZW4nICkKYGBgCmBgYHtyfQpsb2FkKCJwcmltYXRlX2RhdGEvR1NFMTE4NTQ2X21hY2FxdWVfZm92ZWFfYWxsXzEwWF9KYW4yMDE4LlJkYXRhIikKCmRpbW5hbWVzKENvdW50Lm1hdF9mb3ZlYSlbWzFdXSA8LSB0b2xvd2VyKGRpbW5hbWVzKENvdW50Lm1hdF9mb3ZlYSlbWzFdXSkKbWFjYXF1ZV9mb3ZlYV9zZXVyYXQgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KENvdW50Lm1hdF9mb3ZlYSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2plY3QgPSAibWFjYXF1ZV9mb3ZlYSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluLmNlbGxzID0gMywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4uZmVhdHVyZXMgPSAyMDApCgojIGdpdmUgbWFjYXF1ZSBkdGEgdW5pZm9ybSBuYW1lIGluICJvcmlnLmlkZW50IiBtZXRhZGF0YSBjb2x1bW4KQWRkTWV0YURhdGEobWFjYXF1ZV9mb3ZlYV9zZXVyYXQsIAogICAgICAgICAgICBtZXRhZGF0YSA9IG1hY2FxdWVfZm92ZWFfc2V1cmF0W1sib3JpZy5pZGVudCJdXSwgCiAgICAgICAgICAgIGNvbC5uYW1lID0gIm9yaWcuc2FtcGxlLm5hbWUiKQptYWNhcXVlX2ZvdmVhX3NldXJhdFtbIm9yaWcuaWRlbnQiXV0gPC0gIm1hY2FxdWVfZm92ZWEiCgptYWNhcXVlX2ZvdmVhX3NldXJhdApgYGAKQ2xlYW51cApgYGB7cn0Kcm0oaHVtYW4uY291bnRfbWF0cml4LCBodW1hbi5nZW5lX25hbWVzLCBodW1hbi5zYW1wbGVfYW5ub3RhdGlvbnMpCnJtKGNvdW50X21hdHJpeF9VUkwsIGdlbmVfbmFtZXNfVVJMLCBzYW1wbGVfYW5ub3RhdGlvbnNfVVJMLCBpbXBvcnRfcmVtb3RlX2RhdGEpCnJtKG1vdXNlLmRhdGEpCnJtKENvdW50Lm1hdF9mb3ZlYSwgbWFjYXF1ZV9mb3ZlYSkKYGBgCgoKQ29tYmluZQpgYGB7cn0KIyBjb21iaW5lCnJldC5saXN0IDwtIGxpc3QoaHVtYW4gPSBodW1hbl9yZXRfc2V1cmF0LCBtb3VzZSA9IG1vdXNlX3JldF9zZXVyYXQsIG1hY2FxdWUgPSBtYWNhcXVlX2ZvdmVhX3NldXJhdCkKCiMgcHJlcHJvY2VzcwpyZXQubGlzdCA8LSBsYXBwbHkoWCA9IHJldC5saXN0LCBGVU4gPSBmdW5jdGlvbih4KSB7CiAgICB4IDwtIE5vcm1hbGl6ZURhdGEoeCwgdmVyYm9zZSA9IEZBTFNFKQogICAgeCA8LSBGaW5kVmFyaWFibGVGZWF0dXJlcyh4LCBzZWxlY3Rpb24ubWV0aG9kID0gInZzdCIsIG5mZWF0dXJlcyA9IDIwMDAsIHZlcmJvc2UgPSBGQUxTRSkKfSkKCiMgY2xlYW51cApybShodW1hbl9yZXRfc2V1cmF0LCBtb3VzZV9yZXRfc2V1cmF0LCBtYWNhcXVlX2ZvdmVhX3NldXJhdCkKYGBgCgojIEludGVncmF0aW9uCmBgYHtyfQpwbGFuKCJtdWx0aXByb2Nlc3MiLCB3b3JrZXJzID0gNCkKcmV0LmFuY2hvcnMgPC0gRmluZEludGVncmF0aW9uQW5jaG9ycyhvYmplY3QubGlzdCA9IHJldC5saXN0LCBkaW1zID0gMTo1MCwgIGFuY2hvci5mZWF0dXJlcyA9IDEwMDApCnBsYW4oIm11bHRpcHJvY2VzcyIsIHdvcmtlcnMgPSAxKQpyZXQuY29tYmluZWQgPC0gSW50ZWdyYXRlRGF0YShhbmNob3JzZXQgPSByZXQuYW5jaG9ycywgZGltcyA9IDE6NTApCmBgYAoKIyBJbnRlZ3JhdGVkIEFuYWx5c2lzCmBgYHtyfQpwbGFuKCJtdWx0aXByb2Nlc3MiLCB3b3JrZXJzID0gNCkKCkRlZmF1bHRBc3NheShyZXQuY29tYmluZWQpIDwtICJpbnRlZ3JhdGVkIgoKIyBSdW4gdGhlIHN0YW5kYXJkIHdvcmtmbG93IGZvciB2aXN1YWxpemF0aW9uIGFuZCBjbHVzdGVyaW5nCnJldC5jb21iaW5lZCA8LSBTY2FsZURhdGEocmV0LmNvbWJpbmVkLCB2ZXJib3NlID0gRkFMU0UpCnJldC5jb21iaW5lZCA8LSBSdW5QQ0EocmV0LmNvbWJpbmVkLCBucGNzID0gNTAsIHZlcmJvc2UgPSBGQUxTRSkKIyB0LVNORSBhbmQgQ2x1c3RlcmluZwpyZXQuY29tYmluZWQgPC0gUnVuVU1BUChyZXQuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJwY2EiLCBkaW1zID0gMTozNSkKcmV0LmNvbWJpbmVkIDwtIEZpbmROZWlnaGJvcnMocmV0LmNvbWJpbmVkLCByZWR1Y3Rpb24gPSAicGNhIiwgZGltcyA9IDE6MzUpCnJldC5jb21iaW5lZCA8LSBGaW5kQ2x1c3RlcnMocmV0LmNvbWJpbmVkLCByZXNvbHV0aW9uID0gMC4wNzUpCmBgYAojIFVNQVAgVmlzdWFsaXphdGlvbgpgYGB7ciB3YXJuaW5nPUZBTFNFfQpEaW1QbG90KHJldC5jb21iaW5lZCwgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieSA9ICJvcmlnLmlkZW50IikKRGltUGxvdChyZXQuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgbGFiZWwgPSBUUlVFKQpgYGAKYGBge3IsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSAzfQpEaW1QbG90KHJldC5jb21iaW5lZCwgcmVkdWN0aW9uID0gInVtYXAiLCBzcGxpdC5ieSA9ICJvcmlnLmlkZW50IiwgbmNvbCA9IDEpCmBgYAoKIyBJZGVudGlmeSBDbHVzdGVycyB3aXRoIENhbm9uaWNhbCBNYXJrZXJzCmBgYHtyfQpEZWZhdWx0QXNzYXkocmV0LmNvbWJpbmVkKSA8LSAiUk5BIgoKZmVhdHVyZXMgPC0gdG9sb3dlcihjKCJQZGU2YSIsIkduYXQyIiwiTmVmbCIsIkNhbWsyYiIsIlRoeTEiLCJHYWQxIiwiU2xjNmE5IiwKICAgICAgICAgICAgICAgICAgICAgICJQY3NrNiIsIlRycG0xIiwiU2VwdDQiLCJHbHVsIiwiQXJyMyIsIkMxcWEiLCJUbTRzZjEiLCAiTWdwIikpCgpGZWF0dXJlUGxvdChvYmplY3QgPSByZXQuY29tYmluZWQsIAogICAgICAgICAgICBmZWF0dXJlcyA9IGZlYXR1cmVzLCAKICAgICAgICAgICAgcHQuc2l6ZSA9IDAuMSwKICAgICAgICAgICAgY29scyA9IGMoImxpZ2h0Z3JleSIsICIjRjI2OTY5IiksCiAgICAgICAgICAgIG1pbi5jdXRvZmYgPSAicTkiLAogICAgICAgICAgICBjb21iaW5lID0gVFJVRSkgJiBOb0xlZ2VuZCgpICYgTm9BeGVzKCkKYGBgCgoqIFJvZCA6IHBkZTZhCiogQUMgKGFtYWNyaW5lIGNlbGwpIDogZ2FkMSwgc2xjNmE5CiogTUcgKE3DvGxsZXIgZ2xpYSkgOiBnbHVsCiogQkMgKGJpcG9sYXIgY2VsbCkgOiBUcnBtLCBjYW1rMmIKKiBDQyAoY29uZSBjZWxsKSA6IGduYXQyLCBhcnIzCiogUkdDIChyZXRpbmFsIGdhbmdsaWFsIGNlbGwpIDogbmVmbCwgdGh5MQoqIFZDICh2YXNjdWxhciBjZWxsKSA6IG1ncCwgdG00c2YxCiogTSAobWljcm9nbGlhKSA6IGMxcWEKKiBIQyAoaG9yaXpvbnRhbCBjZWxsKSA6IHNlcHQ0CgpNYXJrZXJzIHdlcmUgZGV0ZXJtaW5lZCBmcm9tIFt0aGlzXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTQ2Ny0wMTktMTI3ODAtOCkgcGFwZXIgYW5kIG90aGVyIHNvdXJjZXMuCmBgYHtyfQpyZXQuY29tYmluZWQgPC0gUmVuYW1lSWRlbnRzKHJldC5jb21iaW5lZCwgYDBgID0gIk1HIiwgYDFgID0gIlJvZCIsIGAyYCA9ICJSR0MiLCAKICAgIGAzYCA9ICJSR0MiLCBgNGAgPSAiQkMiLCBgNWAgPSAiQ0MiLCBgNmAgPSAiQkMiLCBgN2AgPSAiQUMiLCBgOGAgPSAiQkMiLCBgOWAgPSAiUkdDIiwgCiAgICBgMTBgID0gIlJHQyIsIGAxMWA9ICJIQyIsIGAxMmAgPSAiTUciLCBgMTNgID0gIlZDIiwgYDE0YCA9ICJSR0MiLCBgMTVgID0gIlJHQyIsIGAxNmAgPSAiTSIsIGAxN2AgPSAiUkdDIikKCkRpbVBsb3QocmV0LmNvbWJpbmVkLCBsYWJlbCA9IFRSVUUpCmBgYAoKCiMgRmluZCBEaWZmZXJlbnRpYWxseSBFeHByZXNzZWQgR2VuZXMKYGBge3J9CmNlbGxzLnR5cGVzIDwtIGMoIlJvZCIsICJCQyIsICJNRyIsICJSR0MiLCAiQ0MiLCAiQUMiLCAiVkMiLCAiSEMiLCAiTSIpCnRoZW1lX3NldCh0aGVtZV9jb3dwbG90KCkpCgpjZWxsX3R5cGVfYXZnIDwtIGZ1bmN0aW9uKHNldXJhdC5jb21iaW5lZCwgaWRlbnQpIHsKICBjZWxscy54IDwtIHN1YnNldChzZXVyYXQuY29tYmluZWQsIGlkZW50cyA9IGlkZW50KQogIElkZW50cyhjZWxscy54KSA8LSAib3JpZy5pZGVudCIKICBjZWxscy54LmF2ZyA8LSBsb2cxcChBdmVyYWdlRXhwcmVzc2lvbihjZWxscy54LCB2ZXJib3NlID0gRkFMU0UpJFJOQSkKICBjZWxscy54LmF2ZyRnZW5lIDwtIHJvd25hbWVzKGNlbGxzLnguYXZnKQogIHJldHVybihjZWxscy54LmF2ZykKfQoKY2VsbHMucGxvdCA8LSBhcy5saXN0KGNlbGxzLnR5cGVzKQpjZWxscy5wbG90IDwtIGxhcHBseShjZWxscy5wbG90LCBGVU4gPSBmdW5jdGlvbih4KSB7CiAgY2VsbHMueC5hdmcgPC0gY2VsbF90eXBlX2F2ZyhyZXQuY29tYmluZWQsIGlkZW50ID0geCkKICB4IDwtIGdncGxvdChjZWxscy54LmF2ZywgYWVzKGh1bWFuX3JldCwgbW91c2VfcmV0KSkgKyBnZW9tX3BvaW50KHNpemUgPSAwLjEpICsgZ2d0aXRsZSh4KQogIHJldHVybih4KQp9KQoKIyBGb3IgaW5kaXZpZHVhbCBwbG90cwojIGZvciAocCBpbiBjZWxscy5wbG90KSB7CiMgICBwcmludChwKQojIH0KCiMgRm9yIGdyaWQgcGxvdApjb3dwbG90OjpwbG90X2dyaWQocGxvdGxpc3QgPSBjZWxscy5wbG90LCBuY29sID0gMykKYGBgCmBgYHtyfQpyZXQuY29tYmluZWQkY2VsbHR5cGUub3JnYW5pc20gPC0gcGFzdGUoSWRlbnRzKHJldC5jb21iaW5lZCksIHJldC5jb21iaW5lZCRvcmlnLmlkZW50LCBzZXAgPSAiXyIpCnJldC5jb21iaW5lZCRjZWxsdHlwZSA8LSBJZGVudHMocmV0LmNvbWJpbmVkKQpJZGVudHMocmV0LmNvbWJpbmVkKSA8LSAiY2VsbHR5cGUub3JnYW5pc20iCmBgYApgYGB7cn0KY2VsbHMuZGlmZmdlbmVzIDwtIGFzLmxpc3QoY2VsbHMudHlwZXMpCmNlbGxzLmRpZmZnZW5lcyA8LSBsYXBwbHkoY2VsbHMuZGlmZmdlbmVzLCBGVU4gPSBmdW5jdGlvbih4KSB7CiAgbGFiX2h1bWFuIDwtIHNwcmludGYoIiVzX2h1bWFuX3JldCIsIHgpCiAgbGFiX21vdXNlIDwtIHNwcmludGYoIiVzX21vdXNlX3JldCIsIHgpCiAgcmV0dXJuKEZpbmRNYXJrZXJzKHJldC5jb21iaW5lZCwgaWRlbnQuMSA9IGxhYl9odW1hbiwgaWRlbnQuMiA9IGxhYl9tb3VzZSkpCn0pCgoKZm9yKGkgaW4gc2VxX2Fsb25nKGNlbGxzLmRpZmZnZW5lcykpIHsKCXggPC0gY2VsbHMuZGlmZmdlbmVzW1tpXV0KCXggPC0gY2JpbmQoeCwgbG9ncCA9IC1sb2cxMCh4JHBfdmFsKSwgdHlwZXMgPSBjZWxscy50eXBlc1tbaV1dLCBnZW5lcyA9IHJvd25hbWVzKHgpKQoJeCA8LSB4WyFncmVwbCgibXQtIiwgeCRnZW5lcyksXSAjIHJlbW92ZSBtaXRvY2hvbmRyaWFsIGdlbmVzCgljZWxscy5kaWZmZ2VuZXNbW2ldXSA8LSB4CglybSh4KQp9CmBgYApUYWJsZXMgd2l0aCB0aGUgbW9zdCBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMgaW4gZWFjaCBjZWxsIHN1YnR5cGU6CmBgYHtyfQpmb3IoaSBpbiBzZXFfYWxvbmcoY2VsbHMuZGlmZmdlbmVzKSkgewogIHByaW50KGtuaXRyOjprYWJsZShoZWFkKGNlbGxzLmRpZmZnZW5lc1tbaV1dKSxjYXB0aW9uPWNlbGxzLnR5cGVzW1tpXV0pKQp9CmBgYApTYXZlIGFzIGNzdiBmaWxlcwpgYGB7cn0KZm9yKGkgaW4gc2VxX2Fsb25nKGNlbGxzLmRpZmZnZW5lcykpIHsKICB3cml0ZS5jc3YoY2VsbHMuZGlmZmdlbmVzW1tpXV0sIHNwcmludGYoInJlc3VsdHMvJWRfJXMuY3N2IiwgaSwgY2VsbHMudHlwZXNbW2ldXSkpCn0KYGBgCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQpnZW5lc190b19wbG90IDwtIDMKZm9yIChpIGluIHNlcV9hbG9uZyhjZWxscy50eXBlcykpIHsKICBwcmludChGZWF0dXJlUGxvdChvYmplY3QgPSByZXQuY29tYmluZWQsIAogICAgICAgICAgICAgIGZlYXR1cmVzID0gcm93bmFtZXMoY2VsbHMuZGlmZmdlbmVzW1tpXV0pWzE6Z2VuZXNfdG9fcGxvdF0sIAogICAgICAgICAgICAgIHNwbGl0LmJ5ID0gIm9yaWcuaWRlbnQiLCAKICAgICAgICAgICAgICBtYXguY3V0b2ZmID0gMywgCiAgICAgICAgICAgICAgY29scyA9IGMoImdyZXkiLCAicmVkIiksCiAgICAgICAgICAgICAgcHQuc2l6ZSA9IDAuMDcsCiAgICAgICAgICAgICAgY29tYmluZSA9IFRSVUUsCiAgICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IDAuNQogICAgICAgICAgICAgICkgKyBwbG90X2Fubm90YXRpb24odGl0bGUgPSBjZWxscy50eXBlc1tbaV1dKSAmIE5vTGVnZW5kKCkgJiBOb0F4ZXMoKQogICAgICAgICkKfQpgYGAKCkNoZWNrIGNlbGwgcHJvcG9ydGlvbiBmb3IgZWFjaCBzcGVjaWVzOgpgYGB7cn0Ka25pdHI6OmthYmxlKHByb3AudGFibGUoeCA9IHRhYmxlKElkZW50cyhyZXQuY29tYmluZWQpLCByZXQuY29tYmluZWRAbWV0YS5kYXRhJG9yaWcuaWRlbnQpLCBtYXJnaW4gPSAyKSkKYGBgCgojIEdlbmUgRW5yaWNobWVudCBBbmFseXNpcwpgYGB7ciB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoZGF0YS50YWJsZSkKY2VsbHMuZGlmZmdlbmVzLmNvbWJpbmVkIDwtIHJiaW5kbGlzdChjZWxscy5kaWZmZ2VuZXMpCgojIFByZXByb2Nlc3NpbmcKIyBmb3IoaSBpbiBzZXFfYWxvbmcoY2VsbHMuZGlmZmdlbmVzLmNvbWJpbmVkKSkgewojIAlpZiAoY2VsbHMuZGlmZmdlbmVzLmNvbWJpbmVkW2ldJGxvZ3AgPiA3NTApIHsKIyAJCWNlbGxzLmRpZmZnZW5lcy5jb21iaW5lZFtpXSRsb2dwIDwtIDc0OQojIAl9CiMgCWlmIChjZWxscy5kaWZmZ2VuZXMuY29tYmluZWRbaV0kYXZnX2xvZ0ZDID4gMykgewojIAkJY2VsbHMuZGlmZmdlbmVzLmNvbWJpbmVkW2ldJGF2Z19sb2dGQyA8LSAyLjk5CiMgCX0KIyB9CiMgCiMgY2VsbHMuZGlmZmdlbmVzLmNvbWJpbmVkJGxvZ3AgPC0gZ3N1YigiSW5mIiwgNzQ5LCBjZWxscy5kaWZmZ2VuZXMuY29tYmluZWQkbG9ncCkKCmdncGxvdChkYXRhPWNlbGxzLmRpZmZnZW5lcy5jb21iaW5lZCwgCgkJICAgYWVzKHg9YXZnX2xvZ0ZDLHk9bG9ncCwgY29sb3VyPXR5cGVzLCBsYWJlbCA9IGdlbmVzKSkgKyAKCWdlb21fcG9pbnQoc2l6ZT0wLjIpICsgCgl0aGVtZV9idygpICsgCgl0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSksIAoJCSAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAgCgkJICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLCAKCQkgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIAoJCSAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKCWxhYnMoeCA9ICJsb2cyKEZvbGQgY2hhbmdlcylcbigzSy9XVCkiLCB5ID0iLWxvZzEwKHAgdmFsdWUpIikgKwoJc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cz1jKC0zLCAzKSkgKwoJc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDEsIDMwMCkpICsKCWdlb21faGxpbmUoeWludGVyY2VwdD0gMSwgY29sb3VyPSJncmV5IiwgbGluZXR5cGU9ImRhc2hlZCIsIHNpemU9MC43ICkgKwoJZ2VvbV92bGluZSh4aW50ZXJjZXB0PSAwICwgY29sb3VyPSJncmV5IiwgICBzaXplPTAuNykKCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSA2LCBkcGkgPSA0MDAsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoc3RyaW5ncikKcGxvdF9lbnJpY2htZW50IDwtIGZ1bmN0aW9uKHR5cGUgPSAiUm9kIiwgaW5mbyA9ICIiKSB7CglpZiAoaW5mbyAhPSAiIikgeyBpbmZvLnN0ciA8LSBzcHJpbnRmKCJfJXMiLCBpbmZvKSB9CgllbHNlIHtpbmZvLnN0ciA8LSAiIn0KCWZpbGVfcGF0aCA8LSBzcHJpbnRmKCJlbnJpY2hfZGF0YS8lcyVzLnR4dCIsIHR5cGUsIGluZm8uc3RyKQoJeCA8LSByZWFkLnRhYmxlKGZpbGVfcGF0aCwgaGVhZGVyPVQsIHNlcD0iXHQiLCBza2lwID0gMTEpCgljb2xuYW1lcyh4KSA8LSBnc3ViKCJ1cGxvYWRfMS4uZm9sZC5FbnJpY2htZW50LiIsICJGb2xkX0VucmljaG1lbnQiLCBjb2xuYW1lcyh4KSkKCWNvbG5hbWVzKHgpIDwtIGdzdWIoInVwbG9hZF8xLi5GRFIuIiwgIkZEUiIsIGNvbG5hbWVzKHgpKQoJY29sbmFtZXMoeCkgPC0gZ3N1YigiR08uYmlvbG9naWNhbC5wcm9jZXNzLmNvbXBsZXRlIiwgIkdPIiwgY29sbmFtZXMoeCkpCgljb2xuYW1lcyh4KSA8LSBnc3ViKCJIb21vLnNhcGllbnMuLi5SRUZMSVNULi4yMDg1MS4iLCAiQ291bnQiLCBjb2xuYW1lcyh4KSkKCXgkR088LSBmYWN0b3IoeCRHTywgbGV2ZWxzID0geCRHT1tvcmRlcih4JEZvbGRfRW5yaWNobWVudCwgZGVjcmVhc2luZyA9RildKQoJeCA8LSB4W29yZGVyKHgkRkRSKSxdCgl4IDwtIHhbMToxMCxdCglnPC0gZ2dwbG90KGRhdGE9eCwgYWVzKHg9Rm9sZF9FbnJpY2htZW50LCB5PUdPLCBjb2xvdXI9RkRSKSkgKyAKCQlnZW9tX3BvaW50KGFlcyhzaXplPUNvdW50KSkgKyAKCQl0aGVtZV9idygpICsKCQl0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSkgLCAKCQkJICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLCAKCQkJICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpICwgCgkJCSAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCgkJCSAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKCQlsYWJzKHggPSAiRm9sZCBFbnJpY2htZW50IiwgeSA9IiAiKSArCgkJc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdyA9ICJyZWQiLCBoaWdoID0gImJsdWUiKSArCgkJc2NhbGVfeV9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDUwKSkgKwoJCWdndGl0bGUodHlwZSwgaW5mbykKCXJldHVybihnKQp9Cgpmb3IgKHR5cGUgaW4gY2VsbHMudHlwZXNbLWMoNiw0KV0pIHsKCXByaW50KHBsb3RfZW5yaWNobWVudCh0eXBlID0gdHlwZSwgaW5mbyA9ICJ0b3AyMDAiKSkKCXJtKHR5cGUpCn0KcGxvdF9lbnJpY2htZW50KHR5cGUgPSAiUkdDIikKcGxvdF9lbnJpY2htZW50KHR5cGUgPSAiQUMiKQpgYGAKCg==